package org.quipu.client;

import com.google.gwt.dom.client.Style;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import org.quipu.client.events.AppEvent;
import org.quipu.client.events.AppEventBus;
import org.quipu.client.events.TagChangedEvent;
import org.quipu.client.events.TagsOutOfDateEvent;
import org.quipu.dto.TagDto;

public class TagsCloud extends AppEventWidget {

    private final VerticalPanel vPanel;
    private final FlowPanel toggledDiv;
    private final FlowPanel cloudDiv;
    private final Map<Long, TagDto> tags;
    private final Map<Long, TagDto> toggledTags;
    private final TextBox newTagInput;
    private final Button newTagBtn;
    private final boolean editable;
    private final List<ControlChangeHandler<TagsCloud, Long>> changeHandlers;
    final int minFrequency    = 0;
    final int maxFrequency    = 200;
    final int MIN_FONT_SIZE = 7;
    final int MAX_FONT_SIZE = 18;

    public TagsCloud(AppEventBus eventBus, AppCache cache, boolean editable) {
        super(eventBus, cache);

        tags = new HashMap<Long, TagDto>();
        toggledTags = new LinkedHashMap<Long, TagDto>();
        changeHandlers = new LinkedList<ControlChangeHandler<TagsCloud, Long>>();

        this.editable = editable;
        if(editable) {
            newTagInput = new TextBox();
            newTagInput.addStyleName("titleShortInput");
            newTagInput.addStyleName("flowContent");
            newTagBtn = new Button("+");
            newTagBtn.setStylePrimaryName("AddRemoveBtn");
            newTagBtn.addStyleName("flowContent");
            newTagBtn.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    submitTag();
                }
            });
        } else {
            newTagInput = null;
            newTagBtn = null;
        }

        toggledDiv = new FlowPanel();
        toggledDiv.setStylePrimaryName("cloudPanel");
        cloudDiv = new FlowPanel();
        cloudDiv.setStylePrimaryName("cloudPanel");
        vPanel = new VerticalPanel();
//        vPanel.addStyleName("gridPanel");
        vPanel.add(toggledDiv);
        vPanel.add(cloudDiv);

        updateTags();
    }

    @Override
    public void init() {
        getEventBus().addListener(TagsOutOfDateEvent.class, this);
    }

    @Override
    public Widget getWidget() {
        return vPanel;
    }

    @Override
    public void handleEvent(AppEvent event) {
        if(event != null && event instanceof TagsOutOfDateEvent) {
            updateTags();
        }
    }

    private void addCloudTag(TagDto tag) {
        tags.put(tag.getId(), tag);
    }

    private void addCloudTags(Collection<TagDto> tags) {
        for(TagDto tag: tags) {
            this.tags.put(tag.getId(), tag);
        }
    }

    private void submitTag() {
        if(editable &&
            newTagInput.getValue() != null &&
            !newTagInput.getValue().isEmpty() &&
            getCache().getCurrentBook() != null) {
            TagDto newTag = new TagDto();
            newTag.setBookId(getCache().getCurrentBook().getId());
            newTag.setTitle(newTagInput.getValue());
            getMainService().submitTag(newTag, new AsyncCallback<Void>() {

                public void onSuccess(Void result) {
                    getEventBus().fireEvent(new TagChangedEvent(TagsCloud.this, null));
                }

                public void onFailure(Throwable caught) {
                    fireError(caught.getMessage());
                }
            });
        }
    }

    private void toggleTag(TagDto tag) {
        if(toggledTags.remove(tag.getId()) == null) {
            toggledTags.put(tag.getId() ,tag);
        }
        repaintToggled();
        
        for(ControlChangeHandler changeHandler: changeHandlers) {
            changeHandler.onChange(this, toggledTags.keySet().toArray(new Long[] {}));
        }
    }

    private void updateTags() {
        tags.clear();
        addCloudTags(getCache().getTags());

        Iterator<Map.Entry<Long, TagDto>> it = toggledTags.entrySet().iterator();
        while(it.hasNext()) {
            if(!tags.containsKey(it.next().getKey())) {
                it.remove();
            }
        }
        repaintToggled();
        repaintCloud();
    }

    private void repaintToggled() {
        toggledDiv.clear();
        Label tagLink;
        for(final TagDto tag: toggledTags.values()) {
            tagLink = new Label(tag.getTitle());
            tagLink.setStylePrimaryName("tag");
            tagLink.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    toggleTag(tag);
                }
            });
            toggledDiv.add(tagLink);
        }
    }

    private void repaintCloud() {
        cloudDiv.clear();
        if(editable) {
            cloudDiv.add(newTagInput);
            cloudDiv.add(newTagBtn);
        }
        Label tagLink;
        for(final TagDto tag: tags.values()) {
            tagLink = new Label(tag.getTitle());
            tagLink.setStylePrimaryName("cloudTag");
            Style linkStyle = tagLink.getElement().getStyle();
            final String font = getLabelSize(tag.getFrequency());
            linkStyle.setProperty("fontSize", font);
            tagLink.addClickHandler(new ClickHandler() {
                @Override
                public void onClick(ClickEvent event) {
                    toggleTag(tag);
                }
            });
            cloudDiv.add(tagLink);
        }
    }

    public String getLabelSize(long frequency) {
        double weight = ((double) frequency / (double)(maxFrequency - minFrequency));
        int fontSize = MIN_FONT_SIZE + (int) Math.round(((double)(MAX_FONT_SIZE - MIN_FONT_SIZE)) * weight);
        return Integer.toString(fontSize) + "pt";
    }

    void setValue(Collection<TagDto> tags) {
        toggledTags.clear();
        for(TagDto tag: tags) {
            toggledTags.put(tag.getId(), tag);
        }
        repaintToggled();
    }

    public Collection<TagDto> getValue() {
        return toggledTags.values();
    }

    public void addChangeHandler(ControlChangeHandler<TagsCloud, Long> changeHandler) {
        changeHandlers.add(changeHandler);
    }
}
